#include "font.h"

#include <stdio.h>
#include <stdint.h>

#include <SDL2/SDL_opengl.h>
#include <ft2build.h>
#include FT_FREETYPE_H

#include "kill.h"
#include "exit_status.h"
#include "dpi.h"

static FT_Library ft;
static FT_Face face;

#define FLAG_TEX_0 1
#define FLAG_TEX_1 2
#define FLAG_DATA 4
#define ALL_INIT 7

static int current_context;
static uint8_t current_context_flag;

typedef struct {
	GLuint texture_0;
	GLuint texture_1;
	float advance_x;
	float advance_y;
	float width;
	float height;
	float x;
	float y;
	uint8_t initialized_flags;
} glyph;

#define GLYPH_TABLE_SIZE 256

static glyph ascii_glyphs[GLYPH_TABLE_SIZE];

void font_init() {
	if (FT_Init_FreeType(&ft)) {
		puts("Failed to initialize FreeType.");
		kill(EX_ERR_FREETYPE);
	}

	for (size_t i = 0; i < GLYPH_TABLE_SIZE; ++i) {
		ascii_glyphs[i] = (glyph){ 0, 0, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0 };
	}
}

void font_load(const char *path) {
	if (face) return;

	if (FT_New_Face(ft, path, 0, &face)) {
		printf("Failed to load font '%s'\n", path);
		kill(EX_ERR_FONT_LOAD);
	}

	FT_Set_Pixel_Sizes(face, 0, dpi_y(16));
}

void font_set_context(int context) {
	current_context = context;
	current_context_flag = context ? FLAG_TEX_1 : FLAG_TEX_0;
}

static int flags_set(uint8_t target, uint8_t flags) {
	return !(~target & flags);
}

static void init_glyph(char index) {
	glyph *target = &ascii_glyphs[index];

	uint8_t flags = target->initialized_flags;
	if (flags_set(flags, ALL_INIT)) return;
	if (flags_set(flags, FLAG_DATA | current_context_flag)) return;

	if (FT_Load_Char(face, index, FT_LOAD_RENDER)) {
		/* Cannot be rendered -- so basically, we have already initialized it */
		target->initialized_flags = ALL_INIT;
		return;
	}
	FT_GlyphSlot g = face->glyph;

	if (!flags_set(flags, FLAG_DATA)) {
		target->advance_x = g->advance.x * 0.015625f;
		target->advance_y = g->advance.y * 0.015625f;
		target->width = g->bitmap.width;
		target->height = g->bitmap.rows;
		target->x = g->bitmap_left;
		target->y = g->bitmap_top;
		target->initialized_flags |= FLAG_DATA;
	}
	if (!flags_set(flags, current_context_flag)) {
		GLuint tex;

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ONE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ONE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ONE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);

		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		glTexImage2D(
			GL_TEXTURE_2D,
			0,
			GL_RGBA,
			g->bitmap.width,
			g->bitmap.rows,
			0,
			GL_RED,
			GL_UNSIGNED_BYTE,
			g->bitmap.buffer
		);

		glBindTexture(GL_TEXTURE_2D, 0);

		if (current_context) {
			target->texture_1 = tex;
		}
		else {
			target->texture_0 = tex;
		}
		target->initialized_flags |= current_context_flag;
	}
}

void font_draw(const char *text, float x, float y) {
	const char* p;
	for (p = text; *p; ++p) {
		init_glyph(*p);
		glyph *g = &ascii_glyphs[*p];

		glBindTexture(GL_TEXTURE_2D, current_context ? g->texture_1 : g->texture_0);

		float x2 = x + g->x;
		float y2 = -y - g->y;
		float w = g->width;
		float h = g->height;

		glBegin(GL_QUADS);
			glTexCoord2f(0, 0);
			glVertex2f(x2, -y2);

			glTexCoord2f(1, 0);
			glVertex2f(x2 + w, -y2);

			glTexCoord2f(1, 1);
			glVertex2f(x2 + w, -y2 - h);

			glTexCoord2f(0, 1);
			glVertex2f(x2, -y2 - h);
		glEnd();

		x += g->advance_x;
		y += g->advance_y;
	}

	glBindTexture(GL_TEXTURE_2D, 0);
}

void font_measure(const char *text, float *width, float *height) {
	*width = 0;
	*height = 0;
	const char* p;
	for (p = text; *p; ++p) {
		init_glyph(*p);
		glyph *g = &ascii_glyphs[*p];

		*width += g->advance_x;
		if (g->height > *height) *height = g->height;
	}
}